package gov.dhs.cis.soa.readers.rsa;

import gov.dhs.cis.soa.Utils;
import gov.dhs.cis.soa.data.Description;
import gov.dhs.cis.soa.data.Processes;

import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;

import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.util.CellRangeAddress;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class ReportRSAProcesses {
	String PROC_ROOT_XPATH = "//packagedElement[@name='007_Processes']";
	String PROC_XPATH = "packagedElement[@xmi:type='uml:Class']";

	String IN_FILE_NAME = "";
	
	final String UML_XMI_NS = "http://schema.omg.org/spec/XMI/2.1";	//for uml file
	final String EMX_XMI_NS = "http://www.omg.org/XMI";	//for emx file
	final String DELIM = ", ";
	public static final String OUT_FILE = "Processes SOD.xls";
	FileOutputStream fstream = null;
    BufferedWriter out = null;
    HSSFWorkbook book = null;
    HSSFSheet sheet = null;
	String[] header = new String[] { "Process", "Stereo Type", "Release", "WIPT", "IST", "Type", "Name" };  
    int nCurrentRow = 0;

	XPathFactory xpathFactory = XPathFactory.newInstance();
	XPathExpression stereoTypeExpr = null;

	private NamespaceContext getNameSpaceCtx () {
		NamespaceContext ctx = new NamespaceContext() {
	        public String getNamespaceURI(String prefix) {
	        	//System.out.println("Name space requested : " + prefix);
	            String uri = null;
	            if (prefix.equals("xmi")) {
	                uri = getNameSpaceURL();
	            }
	            return uri;
	        }
	       
	        // Dummy implementation - not used!
	        public Iterator getPrefixes(String val) { return null; }
	       
	        // Dummy implemenation - not used!
	        public String getPrefix(String uri) { return null; }
	    };
	    return ctx;
	}
	
	private XPathExpression getXPathExpression(String xpathStr) throws Exception {
		XPath xpath = xpathFactory.newXPath();
		xpath.setNamespaceContext(getNameSpaceCtx());
		// XPath Query for showing all nodes value
		XPathExpression expr = xpath.compile(xpathStr);
		return expr;
	}

	public String getNameSpaceURL() {
		if(this.IN_FILE_NAME.endsWith("emx"))
			return EMX_XMI_NS;
		return UML_XMI_NS;
	}

	private NodeList evaluate(Object evalOn, XPathExpression expr) throws Exception {
		Object result = expr.evaluate(evalOn, XPathConstants.NODESET);
		NodeList nodes = (NodeList) result;
		return nodes;
	}
	
	public void transform(String inXML) throws Exception {
		long startTime = System.currentTimeMillis();
		IN_FILE_NAME = inXML;

		Document doc = getXMLDocument(inXML);
		//prep req values
		stereoTypeExpr = getXPathExpression("eAnnotations/details/@key");

		HashMap<String, String> mainClasses = extractMainClasses(doc);
		fstream = new FileOutputStream(OUT_FILE);

	    book = new HSSFWorkbook();
	    sheet = book.createSheet("Report");
		printHeader();
		ArrayList<Processes> servList = extractProcesses(doc, mainClasses);
		if(out != null) {
			out.close();
		}
		if(book != null) {
			CellRangeAddress range = new CellRangeAddress(0, this.nCurrentRow-1, 0, header.length-1);
			sheet.setAutoFilter(range);
			book.write(fstream);
			fstream.close();
		}

		//printXML();
		
		System.out.println("Time took : " + (System.currentTimeMillis() - startTime)/1000);
	}
	
	private HashMap<String, String> extractMainClasses(Document doc) throws Exception {
		HashMap<String, String> classesList = new HashMap<String, String>();

		String[] pkgNames = { "00_Releases", "002_IST", "004_Management Functions", "008_Services" };
		String[] pkgClsNames = { "uml:Component", "uml:Component", "uml:Component", "uml:Class" };
		
		for(int i = 0; i < pkgNames.length; i++) {
			NodeList nodes = evaluate(doc, getXPathExpression("//packagedElement[@name='" + pkgNames[i] + "']"));
			Node procRootFolderNode = nodes.item(0);
			nodes = evaluate(procRootFolderNode, getXPathExpression("packagedElement[@xmi:type='" + pkgClsNames[i] + "']"));
			
			Node servNode = null;
	
			for (int j = 0; j < nodes.getLength(); j++) {
				servNode = nodes.item(j);
				String name = servNode.getAttributes().getNamedItem("name").getNodeValue();
				String sXMI_ID = servNode.getAttributes().getNamedItemNS(getNameSpaceURL(), "id").getNodeValue();
				classesList.put(sXMI_ID, name);
			}
		}
		return classesList;
	}

	private ArrayList<Processes> extractProcesses(Document doc, HashMap<String, String> mainClasses) throws Exception {
		ArrayList<Processes> servList = new ArrayList<Processes>();

		NodeList nodes = evaluate(doc, getXPathExpression(PROC_ROOT_XPATH));
		Node procRootFolderNode = nodes.item(0);
		
		nodes = evaluate(procRootFolderNode, getXPathExpression(PROC_XPATH));
		
		Node procNode = null;

		XPathExpression servObjAttribsExpr = getXPathExpression("ownedAttribute/@name"); //for attributes
		XPathExpression descExpr = getXPathExpression("ownedComment/body/text()");
		
		for (int i = 0; i < nodes.getLength(); i++) {
			Processes process = new Processes();
			
			procNode = nodes.item(i);
			process.name = procNode.getAttributes().getNamedItem("name").getNodeValue();
			String sXMI_ID = procNode.getAttributes().getNamedItemNS(getNameSpaceURL(), "id").getNodeValue();
			Node attribNode = null;
			NodeList procAttrbNodes = evaluate(procNode, servObjAttribsExpr);
			String name = null;
			String value = null;
			String toks[] = null;
			for(int j = 0; j < procAttrbNodes.getLength(); j++) {
				attribNode = procAttrbNodes.item(j);
				toks = attribNode.getNodeValue().split("=");
				if(toks.length != 2)
					continue;
				name = toks[0].trim();
				value = toks[1].trim();
				
				if("Maturity".equalsIgnoreCase(name))
					process.maturity = value;
				else if("Implementation".equalsIgnoreCase(name))
					process.implMethod = value;
				else if("Volumetrics".equalsIgnoreCase(name))
					process.volumetrics = Integer.parseInt(value);
			}
			process.stereotype = getStereoType(procNode);

			//not needed now, but works
			//procAttrbNodes = evaluate(procNode, descExpr);
			//parseDescription(process, procAttrbNodes.item(0).getNodeValue());
			
			parseAssociations(procRootFolderNode, mainClasses, process, sXMI_ID);

			parseDependencies(doc, procRootFolderNode, process, sXMI_ID);
			parseTargetingDependencies(doc, procRootFolderNode, process, sXMI_ID);
			
			servList.add(process);
		}
		return servList;
	}
	
	private String getStereoType(Node node) throws Exception {
		//XPathExpression stereoTypeExpr = getXPathExpression("eAnnotations/details/@key");
		NodeList attrbNodes = evaluate(node, stereoTypeExpr);
		if(attrbNodes == null || attrbNodes.getLength() == 0)
			return "NONE";
		String stereotype = attrbNodes.item(0).getNodeValue();
		return stereotype;
	}

	private void parseDescription(Processes procObj, String desc) {
		//System.out.println("DESC:" + desc + ", DELIM:" + delim);
		if(desc == null || desc.trim().length() == 0)
			return;
		procObj.desc = new Description();
		procObj.desc.descTxt = desc;
		String[] dToks = desc.split("\\n");
		String toks[] = null;
		String name = null;
		String value = null;
		for(String tok : dToks) {
			toks = tok.split("=");
			if(toks.length != 2)
				continue;
			name = toks[0].trim();
			value = toks[1].trim();
			if("Description".equalsIgnoreCase(name))
				procObj.descText = value;
			else if("Steps".equalsIgnoreCase(name))
				procObj.steps = value;
			else if("Design Aspects".equalsIgnoreCase(name))
				procObj.designAspects = value;
			else if("Business Process".equalsIgnoreCase(name))
				procObj.busProcName = value;
		}
		//return ret;
	}
	
	private void parseAssociations(Node srvRootFolderNode, HashMap<String, String> mainClasses,
			Processes procObj, String sXMI_ID) throws Exception {
		Node attribNode, siblingNode;
		String xp = "//packagedElement[@xmi:type='uml:Association']/ownedEnd[@type='" + sXMI_ID + "']";
		//String xp = "//ownedEnd[@type='" + sXMI_ID + "']";
		//System.out.println(xp);
		XPathExpression associationsExpr = getXPathExpression(xp);
		NodeList associationNodes = evaluate(srvRootFolderNode, associationsExpr);
		for(int j = 0; j < associationNodes.getLength(); j++) {
			attribNode = associationNodes.item(j);
			boolean bFound = false;
			siblingNode = attribNode;
			while (!bFound) {
				siblingNode = siblingNode.getNextSibling();
				if(siblingNode == null || "ownedEnd".equalsIgnoreCase(siblingNode.getNodeName()))
					bFound = true;
			}
			if(siblingNode == null || siblingNode.getAttributes() == null)
				continue;
			String sibType = null;
			if(siblingNode.getAttributes().getNamedItem("type") == null) {
				NodeList tmpNodes = evaluate(siblingNode, getXPathExpression("type/@href"));
				if(tmpNodes != null && tmpNodes.getLength() > 0) {
					sibType = tmpNodes.item(0).getNodeValue();
					sibType = sibType.replaceAll("%20", " ");
				} else {
					System.out.println("**** <<" + procObj.stereotype + ">> " + procObj.name + " has no ownedEnd of type : " + sXMI_ID);
					continue;
				}
			} else {
				sibType = siblingNode.getAttributes().getNamedItem("type").getNodeValue();
			}
			if(Utils.isEmpty(sibType))
				continue;
			String name = mainClasses.get(sibType);
			//System.out.println(name);
			if(Utils.isEmpty(name))
				name = "099_Remove/"+ sibType.substring(sibType.lastIndexOf('/'));
			if(name.startsWith("Release"))
				procObj.release = name;
			else {
				if(name.length() > 4)
					procObj.IST = name;
				else
					procObj.wipt =  name;
			}
		}
	}
	
	private void parseDependencies(Document doc, Node srvRootFolderNode, Processes process, String sXMI_ID) throws Exception {
		Node attribNode;
		XPathExpression associationsExpr;
		NodeList associationNodes;
		String deprootxpath = "//packagedElement[(@client='" + sXMI_ID + "') and (@xmi:type='uml:Dependency')]";
		associationsExpr = getXPathExpression(deprootxpath);
		associationNodes = evaluate(srvRootFolderNode, associationsExpr);
		String supplierid = null;
		
		for(int j = 0; j < associationNodes.getLength(); j++) {
			attribNode = associationNodes.item(j);
			String depname = attribNode.getAttributes().getNamedItem("name").getNodeValue();
			if(attribNode.getAttributes().getNamedItem("supplier") == null) {
				NodeList tmpNodes = evaluate(attribNode, getXPathExpression("supplier/@href"));
				if(tmpNodes != null && tmpNodes.getLength() > 0) {
					supplierid = tmpNodes.item(0).getNodeValue().replaceAll("%20", " ");
					supplierid = supplierid.substring(supplierid.lastIndexOf('/'));
					parseDependency(process, depname, "099_Remove/"+supplierid, "-");
					continue;
				} else {
					System.out.println("**** <<" + process.stereotype + ">> " + process.name + " has no Supplier of type : " + depname);
					continue;
				}
			}
			supplierid = attribNode.getAttributes().getNamedItem("supplier").getNodeValue();
			
			String supxpath = "//packagedElement[@xmi:id='" + supplierid + "']";
			XPathExpression depNodeExpr = getXPathExpression(supxpath);
			NodeList depNodeList = evaluate(doc, depNodeExpr);
			for(int k = 0; k < depNodeList.getLength(); k++) {
				Node depNode = depNodeList.item(k);
				String supName = getAttributeValue(depNode, "name");
				String stereoType = getStereoType(depNode);
				parseDependency(process, depname, supName, stereoType);
			}
		}
	}

	private void parseTargetingDependencies(Document doc, Node srvRootFolderNode, Processes process, String sXMI_ID) throws Exception {
		Node attribNode;
		XPathExpression associationsExpr;
		NodeList associationNodes;
		String deprootxpath = "//packagedElement[(@name='Invokes Process') and (@supplier='" + sXMI_ID + "') and (@xmi:type='uml:Dependency')]";
		associationsExpr = getXPathExpression(deprootxpath);
		associationNodes = evaluate(srvRootFolderNode, associationsExpr);
		String supplierid = null;
		
		for(int j = 0; j < associationNodes.getLength(); j++) {
			attribNode = associationNodes.item(j);
			String depname = attribNode.getAttributes().getNamedItem("name").getNodeValue();
			if(attribNode.getAttributes().getNamedItem("client") == null) {
				NodeList tmpNodes = evaluate(attribNode, getXPathExpression("supplier/@href"));
				if(tmpNodes != null && tmpNodes.getLength() > 0) {
					supplierid = tmpNodes.item(0).getNodeValue().replaceAll("%20", " ");
					supplierid = supplierid.substring(supplierid.lastIndexOf('/'));
					parseDependency(process, depname, "099_Remove/"+supplierid, "-");
					continue;
				} else {
					System.out.println("**** <<" + process.stereotype + ">> " + process.name + " has no Supplier of type : " + depname);
					continue;
				}
			}
			supplierid = attribNode.getAttributes().getNamedItem("client").getNodeValue();
			
			String supxpath = "//packagedElement[@xmi:id='" + supplierid + "']";
			XPathExpression depNodeExpr = getXPathExpression(supxpath);
			NodeList depNodeList = evaluate(doc, depNodeExpr);
			for(int k = 0; k < depNodeList.getLength(); k++) {
				Node depNode = depNodeList.item(k);
				String supName = getAttributeValue(depNode, "name");
				String stereoType = getStereoType(depNode);
				parseDependency(process, depname, supName, stereoType);
			}
		}
	}

	private void parseDependency(Processes procObj, String relationName, String supplierName, String stereoType) {
		//Invokes Interface - is not done
		String type = "NONE";
		if ("acts".equalsIgnoreCase(relationName)) {
			type = "ACTR";
		} else if ("Invokes Service".equalsIgnoreCase(relationName)) {
			type = "SERV";
		} else if ("Invokes Process".equalsIgnoreCase(relationName)) {
			type = stereoType;
		} else if ("Hosts Process".equalsIgnoreCase(relationName)) {
			type = "HOST";
		} else if ("implements".equalsIgnoreCase(relationName)) {
			type = stereoType;
		}
		HSSFRow row = sheet.createRow(nCurrentRow++);
		int i = 0;
		
		//System.out.println("<<" + procObj.stereotype + ">> " + procObj.name + ": <<" + stereoType + ">> " + relationName + " = " + supplierName);
		HSSFCell cell = row.createCell(i++);
		cell.setCellValue(procObj.name);
		cell = row.createCell(i++);
		cell.setCellValue(procObj.stereotype);
		cell = row.createCell(i++);
		cell.setCellValue(procObj.release);
		cell = row.createCell(i++);
		cell.setCellValue(procObj.wipt);
		cell = row.createCell(i++);
		cell.setCellValue(procObj.IST);
		cell = row.createCell(i++);
		cell.setCellValue(type);
		cell = row.createCell(i++);
		cell.setCellValue(supplierName);
	}

	private String getAttributeValue(Node node, String attribName) {
		String ret = "";
		Node attribNode = node.getAttributes().getNamedItem(attribName);
		if(attribNode != null)
			ret = attribNode.getNodeValue();
		return ret;
		
	}
	
	private Document getXMLDocument(String inXML)
			throws ParserConfigurationException, SAXException, IOException {
		InputStream inXMLStream = this.getClass().getClassLoader().getResourceAsStream(inXML);

		DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
		domFactory.setNamespaceAware(true);
		DocumentBuilder builder = domFactory.newDocumentBuilder();
		Document doc = builder.parse(inXMLStream);
		return doc;
	}
	
	private void printHeader() {
		HSSFRow row = sheet.createRow(nCurrentRow++);
		String data = "";
		for(int i = 0; i < header.length; i++) {
			data = (String)header[i];
			HSSFCell cell = row.createCell(i);
			HSSFCellStyle style = book.createCellStyle();
			style.setFillForegroundColor(IndexedColors.LIGHT_ORANGE.getIndex());
			style.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
			style.setAlignment(HSSFCellStyle.ALIGN_CENTER);
			style.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);
			cell.setCellStyle(style);
			cell.setCellValue(data);
		}
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		String modelXMIFIle = "USCISIOEModel1.emx";
		String compModelXMIFile = "IST UML Design Model.uml";
		
		// for Process SOD.xls file only		
//		ReportRSAProcesses readFile = new ReportRSAProcesses();
//		try {
//			readFile.transform(modelXMIFIle);
//		} catch (Exception e) {
//			e.printStackTrace();
//		}
		
		// for ComponentModel.xls file only		
//		ReportRSACompModel readFile = new ReportRSACompModel();
//		try {
//			readFile.transform(compModelXMIFile);
//		} catch (Exception e) {
//			e.printStackTrace();
//		}
		
		// for RateChart.xls file only		
		RateChartExport readFile = new RateChartExport();
		try {
			readFile.transform(modelXMIFIle);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}
